﻿using namespace std;
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

//1.1 auto初始化
void test01()
{
    //使用auto必须立刻初始化
    //auto s;×
    auto a = 3;//auto被推导为整型int
    auto b = 2.14;//auto是double类型
    auto c = 'c';//auto是字符型
    cout << typeid(a).name() << "," << typeid(b).name() << "," << typeid(c).name() << endl;
    cout << sizeof(a) << "," << sizeof(b) << "," << sizeof(c) << endl;

    //auto和其他类型混合使用
    int x = 10;
    auto* p1 = &x;//p1是int*类型,auto是int类型
    cout <<"p1的类型是:"<< typeid(p1).name() << endl;
    auto p2 = &x;//p2是int*类型,auto是int*类型
    cout << "p2的类型是:" << typeid(p2).name() << endl;
    auto& r1 = x;//r1是int&类型,auto是int类型
    cout << "r1的类型是:" << typeid(r1).name() << endl;
    auto r2 = r1;//r1是int&类型,r2也是int&类型,但是通过打印发现r1是整型,即引用类型会打印它的原始类型
    cout << "r2的类型是:" << typeid(r1).name() << endl;
}
//1.2 auto的使用限制
//①auto不能用对于定义数组
//auto arr[] = { 1,2,3 };×,即使马上初始化也不允许
//②auto不能用于类的成员变量
class TestAuto
{
public:
    //auto m_age = 20;×.初始化也不行
    int m_age = 20;
};
//③auto不能用于函数参数
//int add(auto a = 2, auto b = 3) { return a + b };×
//④auto如果作为函数的返回值,可以用于函数的定义,但是不能用于函数的声明
auto mySum(int num)
{
    int sum = 0;
    for (int i = 0; i <= num; i++)
    {
        sum += i;
    }
    return sum;
}
//⑤auto不能用于模板参数
template<class T>
class A
{
    T m_age;
};
A<int>a1;
A<char>a2;
//A<auto>a3,×;

//1.3 auto的使用场合
void test02()
{
    //直接在定义变量的时候使用,
    //在循环语句中使用auto作为循环控制变量
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    for (auto i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    //定义迭代器,使用auto来推导迭代器的类型
    vector<int>v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    //使用auto前
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it<<" ";
    }
    cout << endl;
    //使用auto后
    for (auto it = v.begin(); it != v.end(); it++)
    {
        cout << *it<<" ";
    }
    cout << endl;
}

//2.空指针nullptr
void fun(int n) { cout << n << ",调用的参数为int的函数" << endl; }
void fun(const char* s) { const char* c = "你好"; s = c; cout << s <<",调用的是const char*参数的函数"<< endl; }
void test03()
{
    fun(NULL);//我们发现NULL当作0处理了,调用的是int参数的函数重载,显然不是我们期望的
    fun(nullptr);//我们换成nullptr就不会有问题,会按照我们的期望来调用指针参数的函数
    char* p_char = nullptr;
    int* p_int = nullptr;
    int n = 10;
    //if(n == nullptr)×,nullptr不能跟整型作比较
}

//3.lambda表达式
void test04()
{
    [] {};//这是一个最简单的匿名函数,但是啥也做不了
    int a = 1;
    int b = 2;
    [=] {return a + b; };//值传递捕捉所有变量,返回a+b的值
    //执行匿名函数的时候,只需在后面加上()
    cout << "a+b=" << [=] {return a + b; }() << endl;
    //定义一个带参数的匿名函数
    [](int n1, int n2) {return n1 + n2; };
    cout << "n1+n2=" << [](int n1, int n2) {return n1 + n2; }(2, 4) << endl;
    //定义带返回值的匿名函数
    cout << "a+b=" << [=]()->int {return a + b; }() << endl;//如果有返回值不能省略()
    //结合auto来用
    auto f = [] {return 100; };
    cout << "执行匿名函数f:" << f() << endl;
    //使用引用捕捉
    auto f1 = [&](int c) {b = a + c; };//引用捕捉,可以修改c的值
    f1(10);
    cout << "执行f1之后,a=" << a << "b=" << b << endl;
    //mutable的用法
    int x = 1;
    int y = 2;
    //auto add1 = [x, y]()->int {x *= 2; return x + y;};×,报错是因为x有常性,不允许修改
    auto add1 = [x, y]()mutable->int {x *= 2; return x + y; };//通过mutable取消了捕捉变量的常性
    cout << add1() << endl;//输出4
    //匿名函数也可以在函数内部调用普通函数
    auto fun_mySum = []()->int {return mySum(10); };
    cout << fun_mySum() << endl;
}
//课堂练习:使用匿名函数代替STL中排序器函数,实现排序算法的倒叙,操作一个vector<int>
void test05()
{
    vector<int>v = { 21,2,54,6,4 };
    //遍历容器
    for (auto it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    //
    sort(v.begin(), v.end(), [&](int a, int b)->int {return a > b; });
    for (auto ele : v)
    {
        cout << ele <<" ";
    }
    cout << endl;
}

//4.for范围遍历
void test06()
{
    //使用传统方式遍历
    int arr[6] = { 1,2,3,4,5,6 };
    for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    char arr_c[] = "hello world";
    for (int i = 0; i < strlen(arr_c); i++)
    {
        cout << arr_c[i];
    }
    cout << endl;

    //for范围遍历
    for (auto i : arr)
    {
        cout <<i<< " ";
    }
    cout << endl;
    for (auto c : arr_c)
    {
        cout << c;
    }
    cout << endl;

    //遍历容器
    vector<int>v = { 1,2,3,4,5 };
    for (auto i : v)
    {
        cout << i << " ";
    }
    cout << endl;
}
//通过for循环遍历,实现容器的模板函数,打印vector容器和map容器的模板
template<typename T>
void printVector(vector<T>& v)
{
    for (auto ele : v) { cout << ele << " "; }
    cout << endl;
}
template<typename T1,typename T2>
void printMap(map<T1, T2>& m)
{
    for (auto ele : m) { cout << ele.first << ":" << ele.second << endl; }
}
void test07()
{
    vector<int>i_v = { 1,2,3,4,5 };
    vector<string>s_v = { "hello","world","c++","java" };
    printVector(i_v);
    printVector(s_v);

    map<int, int>i_m = { {1,9},{2,8},{3,7} };
    map<int, string>i_s_m = { {123,"张三"},{234,"李四"},{345,"王五"} };
    printMap(i_m);
    printMap(i_s_m);

    for (auto num : { 24,54,74,76,34 }) { cout << num << " "; }
    cout << endl;

    //for范围遍历字符串时,会把字符串的结束符也遍历上
    int count = 0;
    char arr_char[] = "hello";
    //传统方式
    for (int i=0; i < strlen(arr_char); i++)
    {
        count++;
    }
    cout << count << endl;//输出为5
    //for范围遍历
    count = 0;
    for (auto c : arr_char)
    {
        count++;
    }
    cout << count << endl;
}
//for循环遍历的引用遍历
void test08()
{
    vector<int>i_v = { 1,2,3,4,5 };
    for (auto& i : i_v) { cout << ++i << " "; }//引用的方式遍历,每次都+1后输出
    cout << endl;
    //操作后,vector中的值就改变了
    for (auto& i : i_v) { cout << i << " "; }
    cout << endl;
}

//5.初始化列表
//结构体
struct Point
{
    int px;
    int py;
    Point(int x,int y):px(x),py(y){}
};
//类
class Date
{
public:
    int m_year;
    int m_month;
    int m_day;
    Date(int y, int m, int d) :m_year(y), m_month(m), m_day(d) {}
};
void test09()
{
    int arr1[] = { 4,5,6,7,8 };
    int arr2[5] = { 8 };
    //自定义类型初始化
    Point p = { 10,25 };
    cout << p.px << "," << p.py << endl;
    Date d1 = { 2024,10,25 };
    Date d2 { 2024,10,14 };//省略=
    cout << d1.m_year << "," << d1.m_month << "," << d1.m_day << endl;
    cout << d2.m_year << "," << d2.m_month << "," << d2.m_day << endl;
    //容器的初始化
    vector<int>v_int = { 2,4,64,35,25 };
    vector<Date>v_date = { {2025,1,29},{2025,1,1} };
    //申请堆内存的时候初始化
    Point* pp = new Point[3]{ {1,1},{2,2},{3,3} };
    for (int i = 0; i < 3 ; i++)
    {
        cout << (pp + i) -> px << "," << (pp + i)->py <<" "<< endl;
    }
    int* p_int = new int[5] {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)
    {
        cout <<*(p_int+i) << " "<<endl;
    }

    //初始化列表的类型
    auto ls = { 1,2,3,4 };
    cout << typeid(ls).name() << endl;
    auto cls = { 'a','b','c' };
    cout << typeid(cls).name() << endl;
}

//6.类型别名
void test10()
{
    typedef Date D;//自定义类型
    typedef double dou;//基本类型
    using P = Point;//using
    //使用别名
    D d1 = { 2024,10,25 };
    dou d = 10.25;
    P p1 = { 10,25 };
}

//7.右值引用和移动语句
void test11()
{
    int n = 3;
    int& a = n;//左值引用
    //int& b = 5;×,左值引用无法引用右值
    int&& b = 5;//右值引用
    const int& b = 5;//const修饰可以引用
    cout << &"hello" << endl;//字符串字面量是左值,可以取地址
    //cout << &10 << endl;×.10是右值,无法取地址

    int n1 = 1;
    int n2 = 2; 
   // cout << &(n1 + n2) << endl;×.表达式计算的结果是临时的,是右值,不能取地址
    int res = n1 + n2;
    cout << &res << endl;//res保存了n1+n2的计算结果,是左值,可以取地址
}

class Student
{
    string name;
    int age;
public:
    Student(string n, int a) :name(n), age(a){}
    void show() { cout << "姓名:" << name <<"," << "年龄:" << age << endl; }
};
void test12()
{
    Student s1("张三", 18);
    cout << "s1:";
    s1.show();
    Student s2(s1);//拷贝构造
    cout << "s2:";
    s2.show();
    Student s3(move(s1));//移动构造,取得对s1的所有权,此时s3相当于s1,这时候s1就被析构了
    cout << "s3:";
    s3.show();
    cout << "s1:";
    s1.show();
    Student s4 = s2;//赋值函数
    cout << "s4:";
    s4.show();
    Student s5 = move(s2);//move将s2转换成右值,然后使用了移动赋值函数,s5相当于原来的s2,s2就被析构了
    cout << "s5:";
    s5.show();
    cout << "s2:";
    s2.show();

    //在容器中使用移动语义
    vector<string>vec_str1 = { "hello","world","welcome" };
    vector<string>vec_str2 = vec_str1;//传统方式
    vector<string>vec_str3 = move(vec_str1);//通过move函数转换成右值,实现调用移动赋值函数,提高效率,避免复制的过程

    vector<vector<int>>res;
    vector<int>temp;
    temp.push_back(5);//添加基本类型,没必要移动,因为基本类型无法优化
    res.push_back(move(temp));//这里使用移动就可以提高效率
}
int main()
{
    //test01();
    //test02();
    //test03();
    //test04();
    //test05();
    //test06();
    //test07();
    //test08();
    //test09();
    //test10();
    //test11();
    test12();

    return 0;
}

/*
c++11新特性
    1.自动推导类型
    2.空指针nullptr
    3.lambda表达式
    4.for范围遍历
    5.初始化列表
    6.类型别名
    7.右值引用和移动语句
    8.智能指针
    9.thread多线程

1.自动类型推导
    1.1 auto初始化
       在c++11之前,auto就已经存在,它用来指定变量的存储类型,它跟static关键字是相对的,代表自动存储
       c++11之后,auto被赋予新的含义,用来做变量类型的自动推导,也就是说使用auto以后,可以不用指定变量类型,auto是可以让编译器自动推导的
       语法:auto 变量名=变量的值;此时auto起到一个占位的作用,编译期间auto会被真正的类型取代,
       注意事项:使用auto时,由于需要根据变量的值来推导具体的值,所以必须对变量立刻初始化,这样才能推导
    1.2 auto的使用限制
       使用auto时必须立刻对变量初始化,除此之外,
         auto不能用于定义数组
         auto不能用于类的成员变量
         auto不能在函数参数中使用,因为形参只有在函数调用的时候才能被赋值初始化
         auto如果作为函数的返回值,可以用于函数的定义,但是不能用于函数的声明
         auto不能用于模板参数
    1.3 auto的使用场合
       直接在定义变量时使用,不再举例说明
       在循环语句中使用auto作为控制变量,比如定义迭代器，使用auto来推导迭代器的类型

 2.空指针nullptr
    空指针NULL,在c++中除了NULL,0也可以代表空指针,所以NULL会当作0处理.
    这种情况,在某些函数调用的时候会存在问题,如果一个函数有重载,重载的是两个不同的参数,一个参数是指针类型，一个参数是int.
    这个时候，当我们传入NULL的时候,NULL会被解析成0,从而调用int参数的函数,显然是不对的.
    为了解决这个问题,c++引入了nullptr来充当空指针
    注意:nullptr是不能跟整型作比较的

3.lambda表达式(匿名函数,即没有名字)
   语法:[捕捉列表](参数列表)mutable->返回值类型{函数体代码;}
   
   [捕捉列表]:它一定是出现在匿名函数的最开始位置,不能少.编译器根据[]来判断这个是匿名函数.它是用来捕捉当前作用域中的变量,供匿名函数使用.
        通过捕捉列表来确定匿名函数在调用时可以使用哪些外部变量
        捕捉列表常见用法:
        []:代表匿名函数不捕获不使用任何外部变量.(如果是个全局变量或者静态变量,匿名函数仍然可以使用)
        [变量a,b,c...]:代表以传值的方式来使用多个外部的变量(值捕捉过来的变量具有常性,常量的特性,也就是说不能在匿名函数中修改这些变量)
        [变量&a,&b,&c...]:代表以引用的方式来使用多个外部变量(引用捕捉不具有常性,可以修改它们的值)
        [=]:代表捕捉所有变量都是以值传递的方式捕捉
        [&]:代表捕捉所有变量都是以引用传递的方式捕捉

    (参数列表):它跟普通函数用法一致.如果不传参,可以将()省略不写.但是如果我们指定了返回值的时候,()就不能省略了,同时参数是没用常性的,可以修改
    
    mutable:默认情况下,值捕捉的变量具有常性,不能修改,但是加上mutable之后,就可以取消常性,从而在匿名函数体内部可以修改它们的值,但仍然不能修改原值
        除非使用引用捕捉才可以修改原值.同时,如果加了mutable,()也不能省略,即使为空.

    ->返回值类型:代表匿名函数的返回值类型.如果匿名函数没有返回值,可以省略不写.或者返回值明确的情况下,也可以不写

    {函数体代码}:这里面是匿名函数的实现逻辑.这里面可以使用捕捉过来的变量.也可以使用自己的参数和自定义的变量.

4.for范围遍历
    它是一种新的遍历方式,可以对一个可遍历的序列进行逐个遍历,但是无法指定遍历的范围,只能遍历全部.如果要指定范围,仍然使用传统的for循环
    for范围遍历语法:for(变量类型 变量名:可遍历的序列){循环体代码};
    注意:这个遍历类型要和序列中元素的类型保持一致,通常可以使用auto来推到,可遍历的序列可以是数组,容器,或一个{初始化序列}.
    for范围遍历引用语法:for(变量类型& 变量名:可遍历的序列){循环体代码};引用遍历可以修改序列的原值

5.初始化列表
    使用{}初始化
    在c++11之前,就可以使用{}对数组进行初始化
    c++11之后,扩大了它的使用范围,可以对所有的内置类型和用户自定义类型进行初始化.初始化时可以加上=,也可以不加=

6.类型别名
    类型别名就是给类型(基本类型和自定义类型)起别名,然后就可以使用别名,等价于原类型名
    typedef 类型原名 类型别名;
    using 类型别名=类型原名;
    以上两种方式只有一个区别,那就是typedef不能用于给模板类区别名,其他情况它俩是等价的

7.右值引用和移动语句
    右值引用也是c++11的重要特性.主要解决性能问题.可以免除某些场景下的一些额外的开销
    先说一下左值和右值:
        左值:可以放在等号左边的值,可以取地址,并且有名字.如函数名,变量名,返回左值引用的函数调用,字符串字面量如"hello"
        右值:只能放在等号右边,不能去地址,也没有名字.比如运算表达式产生的临时结果.除字符串以外的字面量,非引用返回的临时变量,匿名函数
        举例:int a=5;a是左值,可以通过&取地址,5是个字面量,不能取地址,它是右值
    右值引用:
        左值引用是对左值的引用或者叫做别名.右值引用是对右值的引用和别名.在语法上,右值引用是在左值引用的基础上多加一个&
        语法:类型 && 右值引用名=右值;

    为什么要使用右值引用:
        右值引用主要为了解决性能问题,c++11之前程序在运行过程中会产生大量的临时对象,主要有以下几个方面:
            函数的返回值
                最常见的是返回自定义类型,如果没有用对象接收,就会产生临时对象.比如有一个函数返回一个临时对象,但是没用使用它,
                这时编译器会创建一个临时对象来存储这个给返回值.而且将来会调用析构函数将它析构掉.
                当有大量的临时对象产生时,或者临时对象占用的资源很大时,就会对系统性能有较大的影响
            计算结果
                一些表达式的计算结果也是临时对象
            值传递的形参
                比如有个函数值传递一个对象参数,值传递的过程就是做了一次拷贝构造进行复制,这时也会产生临时对象
        c++11之后有了右值引用,就可以解决以上问题,当我们不希望产生临时对象的时候,可以使用移动函数move来将一个对象变成右值,
        通过移动构造的方式或者移动赋值的方式,来取得这个对象的所有权,从而避免拷贝的过程.这样就能提高程序的效率

        关于移动构造和移动赋值:
            c++11之前,一个自定义类型(结构体或者类)如果我们没有手动声明,编译器会帮我们生成四个函数:
            构造函数,拷贝构造函数,赋值运算符重载函数,析构函数
            c++11之后,编译器会多生成2个函数:移动构造函数,移动赋值函数.这样我们就可以通过移动的方式,取得对象的所有权,而不必去复制.
            注意:移动语义针对那些实现了移动构造和移动赋值的自定义类型,对于基本类型没有任何作用
            STL容器几乎都是支持移动语义的,这样就可以通过move转换成右值后,调用移动函数进行移动,避免复制的过程,提高性能
            还有一些特殊的类,只允许移动不允许拷贝.如unique_ptr,thread

        右值引用使用时的注意点:
            右值引用与左值引用一样,都需要马上初始化
            右值引用是无法引用左值的,这时可以使用move函数将它转换为右值
            对于基本类型,没必要使用move函数进行移动,因为基本类型的拷贝开销很小.move函数只是将左值转换成右值,但能不能移动,主要看自定义类型中有没有实现移动
            移动之后的对象会被析构,所以通常时对一些临时对象进行移动,或者对不再使用的对象进行移动.如果还要继续使用对象,就不要移动了
            使用const修饰的左值引用是可以引用右值的,但是无法修改
*/